1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 Normal appliation mode. 7 */ 8 module app_normal; 9 10 import std.algorithm : among; 11 import std.exception : collectException; 12 import logger = std.experimental.logger; 13 14 import code_checker.cli : Config; 15 import code_checker.compile_db : CompileCommandDB; 16 import code_checker.types : AbsolutePath, Path, AbsoluteFileName; 17 18 immutable compileCommandsFile = "compile_commands.json"; 19 20 int modeNormal(ref Config conf) { 21 auto fsm = NormalFSM(conf); 22 return fsm.run; 23 } 24 25 private: 26 27 /** FSM for the control flow when in normal mode. 28 */ 29 struct NormalFSM { 30 enum State { 31 init_, 32 changeWorkDir, 33 checkForDb, 34 genDb, 35 checkGenDb, 36 fixDb, 37 checkFixDb, 38 runRegistry, 39 cleanup, 40 done, 41 } 42 43 struct StateData { 44 int exitStatus; 45 bool hasGenerateDbCommand; 46 bool hasCompileDbs; 47 } 48 49 State st; 50 Config conf; 51 bool removeCompileDb; 52 /// Root directory from which the program where initially started. 53 AbsolutePath root; 54 /// Exit status of used to indicate the success to the user. 55 int exitStatus; 56 57 this(Config conf) { 58 this.conf = conf; 59 } 60 61 int run() { 62 StateData d; 63 d.hasGenerateDbCommand = conf.compileDb.generateDb.length != 0; 64 d.hasCompileDbs = conf.compileDb.dbs.length != 0; 65 66 while (st != State.done) { 67 debug logger.tracef("state: %s data: %s", st, d); 68 69 st = next(st, d); 70 action(st); 71 72 // sync with changed struct members as needed 73 d.exitStatus = exitStatus; 74 } 75 76 return d.exitStatus; 77 } 78 79 /** The next state is calculated. Only dependent on current state and state data. 80 * 81 * These clean depenencies should make it easier to reason about the flow. 82 */ 83 static State next(const State curr, const StateData d) { 84 State next_ = curr; 85 86 final switch (curr) { 87 case State.init_: 88 next_ = State.changeWorkDir; 89 break; 90 case State.changeWorkDir: 91 next_ = State.checkForDb; 92 break; 93 case State.checkForDb: 94 next_ = State.fixDb; 95 if (d.hasGenerateDbCommand) 96 next_ = State.genDb; 97 break; 98 case State.genDb: 99 next_ = State.checkGenDb; 100 if (d.exitStatus != 0) 101 next_ = State.cleanup; 102 break; 103 case State.checkGenDb: 104 next_ = State.fixDb; 105 if (d.exitStatus != 0) 106 next_ = State.cleanup; 107 else if (d.hasCompileDbs) 108 next_ = State.fixDb; 109 break; 110 case State.fixDb: 111 next_ = State.checkFixDb; 112 break; 113 case State.checkFixDb: 114 next_ = State.runRegistry; 115 if (d.exitStatus != 0) 116 next_ = State.cleanup; 117 break; 118 case State.runRegistry: 119 next_ = State.cleanup; 120 break; 121 case State.cleanup: 122 next_ = State.done; 123 break; 124 case State.done: 125 break; 126 } 127 128 return next_; 129 } 130 131 void act_changeWorkDir() { 132 import std.file : getcwd, chdir; 133 134 root = Path(getcwd).AbsolutePath; 135 if (conf.miniConf.workDir != root) 136 chdir(conf.miniConf.workDir); 137 } 138 139 void act_checkForDb() { 140 import std.file : exists; 141 142 removeCompileDb = !exists(compileCommandsFile) && !conf.compileDb.keep; 143 } 144 145 void act_genDb() { 146 import std.process : spawnShell, wait; 147 148 auto res = spawnShell(conf.compileDb.generateDb).wait; 149 if (res != 0) { 150 logger.error("Failed running command to generate the compile_commands.json"); 151 exitStatus = 1; 152 } 153 } 154 155 void act_fixDb() { 156 import std.algorithm : map; 157 import std.array : appender, array; 158 import std.stdio : File; 159 import code_checker.compile_db : fromArgCompileDb; 160 161 logger.trace("Creating a unified compile_commands.json"); 162 163 auto compile_db = appender!string(); 164 try { 165 auto dbs = findCompileDbs(conf.compileDb.dbs); 166 if (dbs.length == 0) { 167 logger.errorf("No %s found in %s", compileCommandsFile, conf.compileDb.dbs); 168 exitStatus = 1; 169 return; 170 } 171 172 auto db = fromArgCompileDb(dbs.map!(a => cast(string) a.dup).array); 173 unifyCompileDb(db, compile_db); 174 File(compileCommandsFile, "w").write(compile_db.data); 175 } catch (Exception e) { 176 logger.errorf("Unable to process %s", compileCommandsFile); 177 logger.error(e.msg); 178 exitStatus = 1; 179 } 180 } 181 182 void act_runRegistry() { 183 import std.algorithm : map; 184 import std.array : array; 185 import code_checker.engine; 186 import code_checker.compile_db : fromArgCompileDb, parseFlag, 187 CompileCommandFilter; 188 189 Environment env; 190 env.compileDbFile = AbsolutePath(Path(compileCommandsFile)); 191 env.compileDb = fromArgCompileDb([env.compileDbFile]); 192 env.files = () { 193 if (conf.analyzeFiles.length == 0) 194 return env.files = env.compileDb.map!(a => cast(string) a.absoluteFile.payload) 195 .array; 196 else 197 return conf.analyzeFiles.dup; 198 }(); 199 env.genCompileDb = conf.compileDb.generateDb; 200 env.flagFilter = conf.compileDb.flagFilter; 201 202 env.staticCode = conf.staticCode; 203 env.clangTidy = conf.clangTidy; 204 env.compiler = conf.compiler; 205 env.logg = conf.logg; 206 207 Registry reg; 208 reg.put(new ClangTidy, Type.staticCode); 209 exitStatus = execute(env, reg) == Status.passed ? 0 : 1; 210 } 211 212 void act_cleanup() { 213 import std.file : remove, chdir; 214 215 if (removeCompileDb) 216 remove(compileCommandsFile).collectException; 217 218 chdir(root); 219 } 220 221 /// Generate a callback for each state. 222 void action(const State st) { 223 string genCallAction() { 224 import std.format : format; 225 import std.traits : EnumMembers; 226 227 string s; 228 s ~= "final switch(st) {"; 229 static foreach (a; EnumMembers!State) { 230 { 231 const actfn = format("act_%s", a); 232 static if (__traits(hasMember, NormalFSM, actfn)) 233 s ~= format("case State.%s: %s();break;", a, actfn); 234 else { 235 pragma(msg, __FILE__ ~ ": no callback found: " ~ actfn); 236 s ~= format("case State.%s: break;", a); 237 } 238 } 239 } 240 s ~= "}"; 241 return s; 242 } 243 244 mixin(genCallAction); 245 } 246 } 247 248 auto findCompileDbs(const(AbsolutePath)[] paths) nothrow { 249 import std.algorithm : filter, map; 250 import std.file : exists, isDir, isFile, dirEntries, SpanMode; 251 252 AbsolutePath[] rval; 253 254 static AbsolutePath[] findRecursive(const AbsolutePath p) { 255 import std.path : baseName; 256 257 AbsolutePath[] rval; 258 foreach (a; dirEntries(p, SpanMode.depth).filter!(a => a.isFile) 259 .filter!(a => a.name.baseName == compileCommandsFile).map!(a => a.name)) { 260 try { 261 rval ~= AbsolutePath(Path(a)); 262 } catch (Exception e) { 263 logger.warning(e.msg); 264 } 265 } 266 return rval; 267 } 268 269 foreach (a; paths.filter!(a => exists(a))) { 270 try { 271 if (a.isDir) { 272 logger.tracef("Looking for compilation database in '%s'", a).collectException; 273 rval ~= findRecursive(a); 274 } else if (a.isFile) 275 rval ~= a; 276 } catch (Exception e) { 277 logger.warning(e.msg).collectException; 278 } 279 } 280 281 return rval; 282 } 283 284 /// Unify multiple compilation databases to one json file. 285 void unifyCompileDb(AppT)(CompileCommandDB db, ref AppT app) { 286 import std.algorithm : map, joiner, filter, copy; 287 import std.array : array, appender; 288 import std.ascii : newline; 289 import std.format : formattedWrite; 290 import std.json : JSONValue; 291 import std.path : stripExtension; 292 import std.range : put; 293 import code_checker.compile_db; 294 295 auto flag_filter = CompileCommandFilter(defaultCompilerFilter.filter.dup, 0); 296 logger.trace(flag_filter); 297 298 void writeEntry(T)(ref const T e) { 299 import std.exception : assumeUnique; 300 import std.utf : byChar; 301 302 auto raw_flags = () @safe{ 303 auto app = appender!(string[]); 304 e.parseFlag(flag_filter).flags.copy(app); 305 // add back dummy -c otherwise clang-tidy do not work 306 ["-c", cast(string) e.absoluteFile].copy(app); 307 return app.data; 308 }(); 309 310 formattedWrite(app, `"directory": "%s",`, cast(string) e.directory); 311 312 if (e.arguments.hasValue) { 313 formattedWrite(app, `"arguments": %s,`, raw_flags); 314 } else { 315 formattedWrite(app, `"command": "%-(%s %)",`, raw_flags); 316 } 317 318 if (e.output.hasValue) 319 formattedWrite(app, `"output": "%s",`, cast(string) e.absoluteOutput); 320 formattedWrite(app, `"file": "%s"`, cast(string) e.absoluteFile); 321 } 322 323 if (db.length == 0) { 324 return; 325 } 326 327 formattedWrite(app, "["); 328 329 foreach (ref const e; db[0 .. $ - 1]) { 330 formattedWrite(app, "{"); 331 writeEntry(e); 332 formattedWrite(app, "},"); 333 put(app, newline); 334 } 335 336 formattedWrite(app, "{"); 337 writeEntry(db[$ - 1]); 338 formattedWrite(app, "}"); 339 340 formattedWrite(app, "]"); 341 }